﻿using System;
using System.Collections.Generic;
using System.Security;
using System.Xml;
using Mitosoft.Definitions.General;
using Mitosoft.Definitions.GUI;
using Mitosoft.Definitions.Parentable;

namespace Mitosoft.Definitions.XML
{
  public class XML
  {
    #region Fields

    protected static readonly object padlock = new object();
    protected static XML _instance;

    #endregion

    #region Constructor(s)

    #endregion

    #region Properties

    /// <summary>
    /// Returns a singleton instance
    /// </summary>
    public static XML Instance
    {
      get
      {
        lock (padlock)
        {
          if (_instance == null)
            _instance = new XML();
          return _instance;
        }
      }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Reads the IDisplayable properties from the given XML
    /// and sets the read values in the given object.
    /// </summary>
    /// <param name="xmlTextReader">Reader used to get the values from XML</param>
    /// <param name="iDisplayable">Object to set the read values.</param>
    public void LoadIDisplayable(XmlTextReader xmlTextReader, IDisplayable iDisplayable)
    {
      #region Check parameter(s)

      if (xmlTextReader == null)
        throw new ArgumentNullException(
          "xmlTextReader",
          string.Format(
            "{0}.{1} {2}",
            "XML.XML.Load",
            "needs an non empty xml text reader to be",
            "able to read IDisplayable properties."));

      if (xmlTextReader.NodeType != XmlNodeType.Element ||
          string.IsNullOrEmpty(xmlTextReader.Name) ||
          xmlTextReader.Name != "IDisplayable")
        throw new ArgumentOutOfRangeException(
          "xmlTextReader",
          xmlTextReader,
          string.Format(
            "{0}.{1} {2}",
            "XML.XML.Load",
            "needs the section IDisplayable to",
            "read the properties."));

      #endregion

      while (xmlTextReader.Read())
      {
        switch (xmlTextReader.NodeType)
        {
          case XmlNodeType.Element:

            switch (xmlTextReader.Name)
            {
              case "DisplayText":

                string displayText = string.Empty;

                if (xmlTextReader.Read())
                  displayText =
                    (xmlTextReader.NodeType == XmlNodeType.Text ?
                      xmlTextReader.Value :
                      xmlTextReader.NodeType == XmlNodeType.EndElement ?
                        string.Empty : null);

                if (string.IsNullOrEmpty(displayText))
                  throw new XmlSyntaxException(
                      xmlTextReader.LineNumber,
                      string.Format(
                        "{0} {1} {2}",
                        "XML.XML.Load",
                        "DisplayText is not optional!",
                        "Please check XML syntax."));

                iDisplayable.DisplayText = displayText;

                break;

              case "DisplayOrder":

                string displayOrderText = string.Empty;

                if (xmlTextReader.Read())
                  displayOrderText =
                    (xmlTextReader.NodeType == XmlNodeType.Text ?
                      xmlTextReader.Value :
                      xmlTextReader.NodeType == XmlNodeType.EndElement ?
                        string.Empty : null);

                if (!string.IsNullOrEmpty(displayOrderText))
                {
                  try { iDisplayable.DisplayOrder = Convert.ToInt32(displayOrderText); }
                  catch
                  {
                    throw new XmlSyntaxException(
                      xmlTextReader.LineNumber,
                      string.Format(
                        "{0} {1} {2}",
                        "XML.XML.Load",
                        "Uncorrect display order!",
                        "Please check XML syntax."));
                  }
                }

                break;
            }

            break;

          case XmlNodeType.EndElement:

            switch (xmlTextReader.Name)
            {
              case "IDisplayable":
                return;
            }

            break;
        }
      }

      throw new XmlSyntaxException(
        xmlTextReader.LineNumber,
          string.Format(
            "{0} {1} {2}",
            "XML.XML.Load",
            "Unexpected end reading IDisplayable properties.",
            "Please check XML syntax."));
    }

    /// <summary>
    /// Writes the IDisplayable properties using the given XML writer.
    /// </summary>
    /// <param name="xmlTextWriter">Used to write to XML.</param>
    /// <param name="iDisplayable">Object containing the properties to
    /// be written to XML.</param>
    public void SaveIDisplayable(XmlTextWriter xmlTextWriter, IDisplayable iDisplayable)
    {
      if (iDisplayable == null || xmlTextWriter == null) return;

      xmlTextWriter.WriteStartElement("IDisplayable");

      if (iDisplayable.DisplayOrder != null)
        xmlTextWriter.WriteAttributeString(
          "DisplayOrder",
          iDisplayable.DisplayOrder.ToString().Trim());

      if (iDisplayable.DisplayText != null)
        xmlTextWriter.WriteElementString(
          "DisplayText",
          iDisplayable.DisplayText);

      xmlTextWriter.WriteEndElement();    // "IDisplayable"
    }

    /// <summary>
    /// Reads the IGuidIdentifiable from the given XML
    /// and sets the read values in the given object.
    /// </summary>
    /// <param name="xmlTextReader">Reader used to get the values from XML</param>
    /// <param name="iGuidIdentifiable">Object to set the read values.</param>
    public void LoadIGuidIdentifiable(XmlTextReader xmlTextReader, IGuidIdentifiable iGuidIdentifiable)
    {
      #region Check parameter(s)

      if (xmlTextReader == null)
        throw new ArgumentNullException(
          "xmlTextReader",
          string.Format(
            "{0}.{1} {2}",
            "XML.XML.Load",
            "needs an non empty xml text reader to be",
            "able to read IGuidIdentifiable properties."));

      if (xmlTextReader.NodeType != XmlNodeType.Element ||
          string.IsNullOrEmpty(xmlTextReader.Name) ||
          xmlTextReader.Name != "IGuidIdentifiable")
        throw new ArgumentOutOfRangeException(
          "xmlTextReader",
          xmlTextReader,
          string.Format(
            "{0}.{1} {2}",
            "XML.XML.Load",
            "needs the section IGuidIdentifiable to",
            "read the properties."));

      #endregion

      string guidText = xmlTextReader.GetAttribute("Guid");

      if (string.IsNullOrEmpty(guidText))
        throw new XmlSyntaxException(
          xmlTextReader.LineNumber,
          string.Format(
            "{0}: {1}",
            "XML.XML.Load",
            "needs the property Guid!"));

      iGuidIdentifiable.Guid = new Guid(guidText);

      return;
    }

    /// <summary>
    /// Writes the IGuidIdentifiable properties using the given XML writer.
    /// </summary>
    /// <param name="xmlTextWriter">Used to write to XML.</param>
    /// <param name="iGuidIdentifiable">Object containing the properties to
    /// be written to XML.</param>
    public void SaveIGuidIdentifiable(XmlTextWriter xmlTextWriter, IGuidIdentifiable iGuidIdentifiable)
    {
      if (iGuidIdentifiable == null) return;

      xmlTextWriter.WriteStartElement("IGuidIdentifiable");

      xmlTextWriter.WriteAttributeString(
        "Guid", iGuidIdentifiable.Guid.ToString().Trim());

      xmlTextWriter.WriteEndElement();  // "IGuidIdentifiable"
    }


    /// <summary>
    /// Reads the INParentable objects from the given XML.
    /// For each parent a IGuidDummy object will be created which contains
    /// the read GUID.
    /// </summary>
    /// <param name="xmlTextReader">Reader used to get the values from XML</param>
    /// <param name="iNParentable">Object which will contain the IGuidDummy objects
    /// with the read GUIDs. This object will be initialized if it is null.</param>
    public void LoadINParentable(XmlTextReader xmlTextReader, INParentable iNParentable)
    {
      #region Check parameter(s)

      if (xmlTextReader == null)
        throw new ArgumentNullException(
          "xmlTextReader",
          string.Format(
            "{0}.{1} {2}",
            "XML.XML.Load",
            "needs an non empty xml text reader to be",
            "able to read INParentable properties."));

      if (xmlTextReader.NodeType != XmlNodeType.Element ||
          string.IsNullOrEmpty(xmlTextReader.Name) ||
          xmlTextReader.Name != "Parents")
        throw new ArgumentOutOfRangeException(
          "xmlTextReader",
          xmlTextReader,
          string.Format(
            "{0}.{1} {2}",
            "XML.XML.Load",
            "needs the section Parents to",
            "read the properties."));

      if (iNParentable.ParentObjects == null)
        iNParentable.ParentObjects = new List<object>();

      #endregion

      IGuidDummy guidDummy = null;

      while (xmlTextReader.Read())
      {
        switch (xmlTextReader.NodeType)
        {
          case XmlNodeType.Attribute:

            switch (xmlTextReader.Name)
            {
              case "PreparedForFutureExtensions":
                break;
            }

            break;

          case XmlNodeType.Element:

            switch (xmlTextReader.Name)
            {
              case "Parent":
                guidDummy = new IGuidDummy();
                break;

              case "IGuidIdentifiable":
                this.LoadIGuidIdentifiable(xmlTextReader, guidDummy);
                iNParentable.ParentObjects.Add(guidDummy);
                break;
            }

            break;

          case XmlNodeType.EndElement:

            switch (xmlTextReader.Name)
            {
              case "Parents":
                return;
            }

            break;
        }
      }

      throw new XmlSyntaxException(
        xmlTextReader.LineNumber,
          string.Format(
            "{0} {1} {2}",
            "XML.XML.Load",
            "Unexpected end reading INParentable!",
            "Please check XML syntax."));
    }

    /// <summary>
    /// Writes the INParentable objects using the given XML writer.
    /// </summary>
    /// <param name="xmlTextWriter">Used to write to XML.</param>
    /// <param name="iNParentable">Object containing the parents which
    /// GUIDs we want to save.</param>
    public void SaveINParentable(XmlTextWriter xmlTextWriter, INParentable iNParentable)
    {
      // Exit if nothing to do:
      if (iNParentable == null || iNParentable.ParentObjects == null ||
          iNParentable.ParentObjects.Count <= 0)
        return;

      xmlTextWriter.WriteStartElement("Parents");

      foreach (object parentObject in iNParentable.ParentObjects)
      {
        if (!(parentObject is IGuidIdentifiable) &&
            !(parentObject is IIdentifiable))
          continue;

        xmlTextWriter.WriteStartElement("Parent");

        if (parentObject is IGuidIdentifiable)
          this.SaveIGuidIdentifiable(xmlTextWriter, (IGuidIdentifiable)parentObject);

        if (parentObject is IIdentifiable)
          xmlTextWriter.WriteElementString(
            "Id",
            ((IIdentifiable)parentObject).Id.ToString().Trim());

        xmlTextWriter.WriteEndElement();  // "Parent"
      }

      xmlTextWriter.WriteEndElement();    // "Parents" 
    }

    #endregion
  }
}
